using System;
using System.Collections.Generic;
using System.Text;

namespace Mpi.Common.Collections
{
    public class Tree<NodeType> : IEnumerable<TreeNode<NodeType>>
    {

        private TreeNode<NodeType> _virtualRoot;
        //private List<TreeNode<NodeType>> innerList;


        //public int Width
        //{
        //    get 
        //    {
        //        int ret = 0;
        //        foreach (TreeNode<NodeType> n in this.innerList)
        //        {
        //            if (n.SubNodes().Length == 0)
        //                ret++;
        //        }
        //        return ret;
        //    }
        //}



        /// <summary>
        /// Gets the number of nodes within this tree
        /// </summary>
        public int Count
        {
            get
            {
                return _virtualRoot.RecursiveSubNodeCount;
            }
        }

        /// <summary>
        /// The virtula root node of this tree.
        /// </summary>
        public TreeNode<NodeType> VirtualRoot
        {
            get { return _virtualRoot; }
        }


        /// <summary>
        /// The standard constructor
        /// </summary>
        public Tree()
        {
            this._virtualRoot = new TreeNode<NodeType>();
            this._virtualRoot.Tree = this;
            //this.innerList = new List<TreeNode<NodeType>>();
        }

        /// <summary>
        /// Calculates the index of a node
        /// </summary>
        /// <param name="node">The node</param>
        /// <returns>The calculated index</returns>
        public int IndexOf(TreeNode<NodeType> node)
        {
            Dictionary<TreeNode<NodeType>, int> dict = new Dictionary<TreeNode<NodeType>, int>();
            int c = 0;
            GetNodeIndxDict(this.VirtualRoot, ref dict, ref c);
            if (dict.ContainsKey(node))
                return dict[node];
            else
                return -1;
        }

        public int IndexOf(NodeType nodecontent)
        {
            TreeNode<NodeType> node = FindNode(nodecontent);
            if (node != null)
            {
                return IndexOf(node);
            }
            else
                throw new ArgumentException();
        }


        /// <summary>
        /// Gets a node by index
        /// </summary>
        /// <param name="indx">The index of the node</param>
        /// <returns>The node looked for</returns>
        /// <exception cref="ArgumentOutOfRangeException">Throws an exeption if the index is not within this tree.</exception>
        public TreeNode<NodeType> this[int indx]
        {
            get
            {
                Dictionary<int, TreeNode<NodeType>> dict = new Dictionary<int, TreeNode<NodeType>>();
                int c = 0;
                GetIndxNodeDict(this.VirtualRoot, ref dict, ref c);
                if (dict.ContainsKey(indx))
                    return dict[indx];
                else
                    throw new ArgumentOutOfRangeException("indx");
            }
        }




        /// <summary>
        /// Recursive helper method to calculate a node->index dictionary
        /// </summary>
        /// <param name="startNode">The start node for indexing</param>
        /// <param name="dict">The dictionary to store indicies</param>
        /// <param name="counter">An index counter</param>
        private void GetNodeIndxDict(TreeNode<NodeType> startNode, ref Dictionary<TreeNode<NodeType>, int> dict, ref int counter)
        {

            for (int i = 0; i < startNode.SubNodeCount; i++)
            {
                dict.Add(startNode[i], counter);
                counter++;
                GetNodeIndxDict(startNode[i], ref dict, ref counter);

            }
        }

        /// <summary>
        /// Recursive helper method to calculate a index-node dictionary
        /// </summary>
        /// <param name="startNode">The start node for indexing</param>
        /// <param name="dict">The dictionary to store indicies</param>
        /// <param name="counter">An index counter</param>
        private void GetIndxNodeDict(TreeNode<NodeType> startNode, ref Dictionary<int, TreeNode<NodeType>> dict, ref int counter)
        {

            for (int i = 0; i < startNode.SubNodeCount; i++)
            {
                dict.Add(counter, startNode[i]);
                counter++;
                GetIndxNodeDict(startNode[i], ref dict, ref counter);

            }
        }

        /// <summary>
        /// Adds a root node.
        /// </summary>
        /// <param name="content">The content of the new node.</param>
        public TreeNode<NodeType> Add(NodeType rootContent)
        {
            TreeNode<NodeType> x = new TreeNode<NodeType>(rootContent);
            Add(this._virtualRoot, x);
            return x;
        }

        /// <summary>
        /// Adds a root node.
        /// </summary>
        /// <param name="content">The the new node.</param>
        public void Add(TreeNode<NodeType> rootNode)
        {

            Add(this._virtualRoot, rootNode);
        }

        /// <summary>
        /// Adds a node to a given parent.
        /// </summary>
        /// <param name="parent">The root of the subtree.</param>
        /// <param name="content">The content of the new node.</param>
        public TreeNode<NodeType> Add(TreeNode<NodeType> parent, NodeType content)
        {
            TreeNode<NodeType> x = new TreeNode<NodeType>(content);
            Add(parent, x);
            return x;
        }



        /// <summary>
        /// Adds a node to a given parent.
        /// </summary>
        /// <param name="parent">The root of the subtree.</param>
        /// <param name="content">The the new node.</param>
        public void Add(TreeNode<NodeType> parent, TreeNode<NodeType> node)
        {
            if (parent.Tree != this)
                throw new ArgumentException("Parent is not a member of this tree", "parent");
            if (node.Tree != null)
                throw new ArgumentException("Node is already a member of another tree", "node");

            node.Parent = parent;
            node.Tree = this;
            parent.Add(node);
            //this.innerList.Add(node);
            //OnNodeAdded(node);
        }


        /// <summary>
        /// Removes a node from the current tree.
        /// </summary>
        /// <param name="remove">The node that will be removed.</param>
        public void Remove(TreeNode<NodeType> remove)
        {

            if (IsVirtualRoot(remove))
                remove.Tree = null;
            else
            {
                if (remove.Parent != null)
                {
                    remove.Parent.Remove(remove); //remove me from my parent list
                    remove.Parent = null;
                }
                foreach (TreeNode<NodeType> tn in remove.SubNodes(false))
                {
                    this.Remove(tn);
                }
            }
            // this.innerList.Remove(remove);
            //OnNodeRemoved(remove);
        }

        /// <summary>
        /// Indicates whether a node is root of this tree or not.
        /// </summary>
        /// <param name="node">The node that will be tested as root.</param>
        /// <returns></returns>
        public bool IsVirtualRoot(TreeNode<NodeType> node)
        {
            return this._virtualRoot == node;
        }


        /// <summary>
        /// Clears the tree
        /// </summary>
        public void Clear()
        {
            List<TreeNode<NodeType>> toremove = new List<TreeNode<NodeType>>();
            foreach (TreeNode<NodeType> tn in this)
            {
                toremove.Add(tn);
            }
            foreach (var item in toremove)
            {
                this.Remove(item);
            }
            this.VirtualRoot.Clear();
            //this.innerList.Clear();
        }


        /// <summary>
        /// Find's a node by it's content. 
        /// </summary>
        /// <param name="content">The content of the wanted node.</param>
        /// <returns>The node which contains the wanted content.</returns>
        public TreeNode<NodeType> FindNode(NodeType content)
        {
            return _virtualRoot.FindNode(content, true);
        }

        /// <summary>
        /// An event indicating that a node was added to the tree
        /// </summary>
        public event EventHandler<NodeEventArgs<NodeType>> NodeAdded;
        internal virtual void OnNodeAdded(TreeNode<NodeType> addedNode)
        {
            if (NodeAdded != null)
            {
                NodeAdded(this, new NodeEventArgs<NodeType>(addedNode));
            }
        }

        public event EventHandler<NodeEventArgs<NodeType>> NodeRemoved;
        internal virtual void OnNodeRemoved(TreeNode<NodeType> removedNode)
        {
            if (NodeRemoved != null)
            {
                NodeRemoved(this, new NodeEventArgs<NodeType>(removedNode));
            }
        }

        //#region IEnumerable<NodeType> Member

        //public IEnumerator<NodeType> GetEnumerator()
        //{
        //    //        return (IEnumerator<NodeType>)this.innerList.GetEnumerator();
        //    throw new NotImplementedException();
        //}

        //#endregion

        //#region IEnumerable Member

        //System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        //{
        //    return this.innerList.GetEnumerator();
        //}

        //#endregion

        #region IEnumerable<TreeNode<NodeType>> Member

        IEnumerator<TreeNode<NodeType>> IEnumerable<TreeNode<NodeType>>.GetEnumerator()
        {
            return _virtualRoot.SubNodes(true).GetEnumerator();
        }

        #endregion

        #region IEnumerable Member

        public System.Collections.IEnumerator GetEnumerator()
        {
            return _virtualRoot.SubNodes(true).GetEnumerator();
        }

        #endregion
    }

    public class NodeEventArgs<NodeType> : EventArgs
    {
        private TreeNode<NodeType> myVar;
        public TreeNode<NodeType> AddedNode
        {
            get { return myVar; }
        }

        public NodeEventArgs(TreeNode<NodeType> addeNode)
        {
            this.myVar = addeNode;
        }

    }
}
